﻿using System;
using System.Diagnostics.Contracts;
using System.IO;
using EnvDTE;
using VsxFactory.Modeling.Strategies;
using VsxFactory.Modeling.VisualStudio;
using Microsoft.VisualStudio.Modeling;
using Microsoft.VisualStudio.Modeling.Validation;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Serialization;
using System.Xaml;
using VsxFactory.Modeling.Strategies.Rules;

namespace VsxFactory.Modeling.StrategyManager.Strategies
{
    //[Strategy("DSL Extension strategy")]
    //class DslSExtensiontrategy : AbstractDslStrategy
    //{
    //    protected override void Generate(StrategyContext ctx, bool force = false)
    //    {
    //        if (ctx.Element == null || ctx.Element is ModelElementExtension)
    //            return;

    //        CodeGenerationEvent codeGenerationEvent = ctx.GenerationEvent;
    //        //if (codeGenerationEvent == CodeGenerationEvent.OnModelElementPropertyChanged)
    //        //    codeGenerationEvent = CodeGenerationEvent.OnModelElementAdded;

    //        var ext = ModelElement.TryGetExtension<ModelElementExtension>(ctx.Element);
    //        if (ext == null || (ext.GenerationEvent & codeGenerationEvent) == CodeGenerationEvent.None)
    //            return;

    //        SolutionManagerService explorer = new SolutionManagerService(ServiceProvider);
    //        var projectNode = explorer.CurrentSolution.GetProject(ext.ProjectName);
    //        if (projectNode == null)
    //            return;

    //        INodeGenerator generator = new T4Generator(ext.TemplateFileName);
    //        generator.Initialize(ctx);
    //        ExpressionEvaluator exp = new ExpressionEvaluator(ctx.Element);
    //        string outputFileName = Path.Combine(projectNode.ProjectDir, exp.Evaluate(ext.OutputFileFormat));
    //        if (File.Exists(outputFileName) && !ext.OverwriteFile)
    //            return;
    //        generator.Generate(ctx, ref outputFileName, null, null);
    //        generator.AddToHierachy(projectNode, null, outputFileName);
    //    }
    //}

    /// <summary>
    /// Strategie permettant d'executer une géneration lors de la modification d'un modéle.
    /// La stratégie consiste à rechercher tous les items ayant une dépendence vers le modéle courant et a executer sur celui-ci
    ///  une génération de type T4.
    /// </summary>
    [Strategy("Dependent file strategy")]
    class DependentFileStrategy : ModelingStrategy
    {
        protected override bool Generate(IStrategyContext ctx, bool force = false)
        {
            System.Diagnostics.Contracts.Contract.Requires(ctx != null);

            bool flag = false;

            CodeGenerationEvent codeGenerationEvent = ctx.GenerationEvent;
            //if (codeGenerationEvent == CodeGenerationEvent.OnModelElementPropertyChanged)
            //    codeGenerationEvent = CodeGenerationEvent.OnModelElementAdded;

            var solutionExplorer = ctx.SolutionExplorer;
            // Recherche pour tous les items de la solution si ils contiennent une dépendance vers le document modifié
            foreach(var hi in solutionExplorer.CurrentSolution.AllElements.OfType<IVsProjectFileItem>())
            {
                try
                {
                    DependenceFileStrategyT4Extender extender = new DependenceFileStrategyT4Extender(ctx.ServiceProvider, hi);
                    // Est ce que ce noeud contient un extender et est ce qu'il est abonné sur l'événement courant ?
                    if ((extender.GenerationEvent & codeGenerationEvent) == CodeGenerationEvent.None)
                        continue; // Non on passe au suivant

                    // A t il une dépendence ?
                    var dependentFileName = extender.DependentFile;
                    if (!String.IsNullOrEmpty(dependentFileName))
                    {
                        // Si oui est ce que celui qui nous concerne ?
                        dependentFileName = Path.Combine(Path.GetDirectoryName(hi.Path), dependentFileName);
                        if (Utils.IsSamePath( ctx.ModelFileName, dependentFileName))
                        {
                            ctx.WriteLogMessage("Detecting dependency on document '{0}' for document '{1}'", dependentFileName, hi.Name);
                            // OUI. Est ce qu'il faut regénérer à chaque fois ? Et si c'est un évenement sur un élement, est il concerné ?
                            if ((hi.AllElements.Count() == 0 || extender.Overwrite) && (force || CheckSpecificModelElement(hi, ctx.Element)))
                            {
                                IVsProjectFileItem masterNode = null;
                                // Si il n'y a pas de template, on considére que le fichier courant en est un.
                                string templateFileName = extender.TemplateFileName;
                                if (String.IsNullOrWhiteSpace(extender.TemplateFileName))
                                {
                                    templateFileName = hi.Path;
                                    masterNode = hi;
                                }

                                INodeGenerator generator = new VsxFactory.Modeling.Strategies.Generators.T4Generator(templateFileName);
                                string outputFileName = Path.ChangeExtension(hi.Path, ".txt"); // Par défaut mais peut changer si le template contient une directive d'extension
                                if (ctx.Element != null)
                                {
                                    ctx.WriteLogMessage("-- Execute T4 generation with template {0}", templateFileName);
                                    // Règle par défaut pour la génération sur un élement spécifique.
                                    GenerationRule rule = new NodeGenerationRule(ctx.Element.GetType())
                                    {
                                        OverwriteFile = extender.Overwrite,
                                        Generators = { generator },
                                        OutputFileNameFormat = !String.IsNullOrWhiteSpace(extender.OutputFileFormat) ? extender.OutputFileFormat : outputFileName
                                    };

                                    var generationElementWrapper = CreateGenerationSpec(ctx, ctx.Element);
                                    rule.Generate(ctx, generationElementWrapper, masterNode);
                                    flag = true;
                                }
                                else
                                {
                                    ctx.WriteLogMessage("-- Execute Custom tool");

                                    // Génération sans élement spécifique (On simule un custom tool)
                                    generator.Initialize(ctx);
                                    var ns = hi.GetAttribute("CustomToolNamespace");
                                    if (String.IsNullOrEmpty(ns))
                                        ns = hi.Project.Namespace;
                                    generator.Generate(ctx, ref outputFileName, null, ns);
                                    generator.AddToSolution(hi.Project, hi, outputFileName);
                                    flag = true;
                                }
                            }
                        }
                    }
                }
                catch(Exception ex)
                {
                    ctx.WriteLogMessage(String.Format("Error when trying to generate code for item {0} - {1}", hi.Name, ex.Message), System.Diagnostics.EventLogEntryType.Error);
                }
            }
            return flag;
        }

        private bool CheckSpecificModelElement(IVsProjectFileItem node, ModelElement mel)
        {
            if (mel == null)
                return true;
            var n = node.GetAttribute("MXModelElementName");
            if (n == null)
                return true;
            var domainClassName = mel.GetDomainClass().Name;
            if (n.IndexOf(',') > 0)
            {
                foreach (var n2 in n.Split(','))
                {
                    if (CheckName(domainClassName, n2))
                        return true;
                }
                return false;
            }
            return CheckName(domainClassName, n);
        }

        private bool CheckName(string domainClassName, string name)
        {
            name = name.Trim();
            if (String.IsNullOrEmpty(name))
                return false;
            int pos = name.IndexOf('*');
            if( pos == 0)
                return domainClassName.EndsWith(name.Substring(1));
            if (pos == name.Length-1)
                return domainClassName.StartsWith(name.Substring(name.Length - 1));
            return domainClassName == name;
        }
    }
}
